#include "config.h"

#include <sys/types.h>
#include <dirent.h>
#include <errno.h>

#define DBG_SUBSYS S_LIBCLUSTER

#include "net_global.h"
#include "cluster.h"
#include "storage.h"
#include "metadata.h"
#include "lich_md.h"
#include "lichstor.h"
#include "lichstor.h"
#include "ynet_rpc.h"
#include "chunk.h"
#include "adt.h"
#include "utils.h"
#include "job_dock.h"
#include "../../storage/storage/stor_rpc.h"
#include "../../storage/controller/volume_proto.h"
#include "../../storage/storage/md_parent.h"
#include "msgqueue.h"
#include "utils.h"
#include "configure.h"
#include "dbg.h"
#include "../../storage/replica/replica.h"

static int __fail__ = 0;
static int __recovery__ = 0;
static int __scan__ = 0;
static int __verbose__ = 0;
static int __deep__ = 0;
static int __lost__ = 0;
static int __needcheck__ = 0;
static int __hasscan__ = 0;
static int __thread__ = 0;
static fileid_t unlink_fid;    //skip /system/unlink directory

#if 0
typedef struct {
        int needretry;
        int parallels;
        char status[MAX_NAME_LEN];
} scan_t;

typedef scan_entry_t lost_entry_t;

scan_t scan = {0, 5, {0x0}};
static int __verbose;
static int __red__ = 0;

#define RECOVER_JOB_MAX 100
#define MSGQUEUE_SIZE (1024 * 1024 * 512) //512M

#if 1

static hashtable_t table = NULL;

typedef struct {
        nid_t nid;
        uint64_t lost;
        uint64_t dirty;
} entry_t;


static uint32_t __key(const void *args)
{
        return ((nid_t *)args)->id;
}

static int __cmp(const void *v1, const void *v2)
{
        const entry_t *ent = (entry_t *)v1;

        //DBUG("cmp %s %s\n", ent->key, (const char *)v2);

        return nid_cmp(&ent->nid, (const nid_t *)v2);
}

static int __lost_table_init()
{
        int ret;

        table = hash_create_table(__cmp, __key, "lost table");
        if (table == NULL) {
                ret = ENOMEM;
                GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

#define SYNC_STAT_LOST 1
#define SYNC_STAT_DIRTY 2

static void __lost_table_update(const nid_t *nid, int stat)
{
        int ret;
        entry_t *ent;

        ent = hash_table_find(table, nid);
        if (ent == NULL) {
                ret = ymalloc((void **)&ent, sizeof(*ent));
                if (unlikely(ret))
                        UNIMPLEMENTED(__DUMP__);

                ent->nid = *nid;
                ent->lost = 0;
                ent->dirty = 0;

                ret = hash_table_insert(table, (void *)ent, &ent->nid, 0);
                if (unlikely(ret))
                        UNIMPLEMENTED(__DUMP__);
        }


        if (stat == SYNC_STAT_LOST)
                ent->lost++;
        else
                ent->dirty++;
}

static int __lost_dump_lost(void *_arg, void *_ent)
{
        entry_t *ent = _ent;

        (void) _arg;

        if (ent->lost) {
                if (__red__)
                        printf("        \x1b[1;31m* %s : lost %llu\x1b[0m\n",
                               network_rname(&ent->nid), (LLU)ent->lost);
                else
                        printf("         %s : lost %llu\n", network_rname(&ent->nid), (LLU)ent->lost);
        }

        return 0;
}

static int __lost_dump_dirty(void *_arg, void *_ent)
{
        entry_t *ent = _ent;

        (void) _arg;

        if (ent->dirty) {
                if (__red__)
                        printf("        \x1b[1;31m* %s : dirty %llu\x1b[0m\n",
                               network_rname(&ent->nid), (LLU)ent->dirty);
                else
                        printf("         %s : dirty %llu\n", network_rname(&ent->nid), (LLU)ent->dirty);
        }

        return 0;
}

static void __lost_table_dump()
{
        hash_iterate_table_entries(table, __lost_dump_dirty, NULL);
        hash_iterate_table_entries(table, __lost_dump_lost, NULL);
}

static void __scan_analysis_check(const chkinfo_t *chkinfo, int stat)
{
        int i, ret;
        const diskid_t *diskid;

        for (i = 0; i < chkinfo->repnum; i++) {
                diskid = &chkinfo->diskid[i];
                ret = network_connect1(diskid);
                if (unlikely(ret)) {
                        __lost_table_update(diskid, stat);
                } else {
                        if (diskid->status == __S_DIRTY || diskid->status == __S_REDIRECT) {
                                __lost_table_update(diskid, SYNC_STAT_DIRTY);
                        }
                }
        }
}

static int __scan_analysis(msgqueue_t *queue, int stat)
{
        int ret;
        struct list_head list, newlist;
        chkinfo_t *chkinfo;
        lost_entry_t *ent;
        struct list_head *pos, *n;

        if (msgqueue_empty(queue))
                return 0;

        __lost_table_init();

        INIT_LIST_HEAD(&list);
        INIT_LIST_HEAD(&newlist);
        while (1) {
                ret = recover_pop(queue, 100, &newlist);
                if (ret < 0) {
                        ret = errno;
                        GOTO(err_ret, ret);
                }

                if (ret == 0)
                        break;

                //__scan_status_update(name, finished, total);

                list_for_each_safe(pos, n, &newlist) {
                        ent = (void *)pos;
                        list_del(&ent->hook);
                        chkinfo =  ent->chkinfo;

                        __scan_analysis_check(chkinfo, stat);

                        yfree((void **)&pos);
                }
        }

        __lost_table_dump();

        return 0;
err_ret:
        return ret;
}
#endif

#endif

static __thread chkid_t __tabid__ = {0, 0, 0};
static __thread char __buf__[MAX_BUF_LEN];

static int __available_raw(const chkinfo_t *chkinfo, char *dump)
{
        int ret, i, available;
        const chkid_t *chkid = &chkinfo->id;
        vfm_t *vfm;
        volid_t volid, tabid;

        cid2tid(&tabid, chkid);
        vfm = (void *)__buf__;
        if (chkid_cmp(&tabid, &__tabid__) != 0) {
                DBUG(""CHKID_FORMAT" load\n", CHKID_ARG(&tabid));

                cid2fid(&volid, chkid);

                ret = md_vfm_get(&volid, chkid, vfm);
                if (unlikely(ret)) {
                        return 0;
                }

                __tabid__ = tabid;
        } else {
                DBUG(""CHKID_FORMAT" exist\n", CHKID_ARG(&tabid));
                vfm = (void *)__buf__;
        }

        chkinfo_dump(chkinfo, vfm, dump, 1);

        available = chkinfo->repnum;
        for (i = 0; i < chkinfo->repnum; i++) {
                if (vfm_exist(vfm, &chkinfo->diskid[i].id)) {
                        available--;
                        continue;
                }

                if (chkinfo->diskid[i].status) {
                        available--;
                        continue;
                }

                ret = network_connect(&chkinfo->diskid[i].id, NULL, 3, 0);
                if (unlikely(ret)) {
                        available--;
                        continue;
                }

                if (!replica_diskonline(chkinfo, i)) {
                        available--;
                        continue;
                }
        }

        return available;
}

static int __available_md(const chkinfo_t *chkinfo, char *dump)
{
        int ret, i, available;

        CHKINFO_STR(chkinfo, dump);
        available = chkinfo->repnum;
        for (i = 0; i < chkinfo->repnum; i++) {
                if (chkinfo->diskid[i].status) {
                        available--;
                        continue;
                }

                ret = network_connect(&chkinfo->diskid[i].id, NULL, 3, 0);
                if (unlikely(ret)) {
                        available--;
                        continue;
                }

                if (!replica_diskonline(chkinfo, i)) {
                        available--;
                        continue;
                }
        }

        return available;
}


static int __available(const chkinfo_t *chkinfo, char *dump)
{
        if (chkinfo->id.type == __RAW_CHUNK__) {
                return __available_raw(chkinfo, dump);
        } else {
                return __available_md(chkinfo, dump);
        }
}

static int __recovery_needcheck(const chkinfo_t *chkinfo)
{
        int available, needcheck;
        uint32_t now;
        static uint32_t prev = 0;
        char buf[MAX_BUF_LEN];

        available = __available(chkinfo, buf);
        if (available == 0) {
                __lost__++;
                printf("\x1b[1;31mlost: %s\x1b[0m\n", buf);
                needcheck = 1;
                return needcheck;
        }

        needcheck = (available != chkinfo->repnum);
        if (needcheck) {
                __needcheck__++;
                if (__verbose__) {
                        printf("unintact: %s\n", buf);
                }
        } else {
                if (__verbose__) {
                        printf("intact: %s\n", buf);
                }
        }

        if (!__verbose__) {
                now = gettime();
                if (now - prev > 2) {
                        prev = now;
                        printf("unintact: %u, lost: %u, scanned: %u, please waiting...\n", __needcheck__, __lost__, __hasscan__);
                }
        }

        return needcheck;
}

static int __recovery_chunk(const nid_t *parentnid,
                            const fileid_t *parent, const chkinfo_t *chkinfo)
{
        int ret, needcheck;//, retry = 0;
        char buf[MAX_BUF_LEN];                

        __hasscan__++;
        needcheck = __recovery_needcheck(chkinfo);
        if (__recovery__) {
                if (needcheck || __deep__) {
                        ret = md_chunk_check(parentnid, parent, &chkinfo->id, -1, 0, 1, NULL);
                        if (ret) {
                                if (ret == ECANCELED) {
                                        printf("recovery chunk chkid:"CHKID_FORMAT" fail skip\n", CHKID_ARG(&chkinfo->id));
                                        goto out;
                                } else {
                                        printf("recovery chunk chkid:"CHKID_FORMAT" fail ret:%d\n", CHKID_ARG(&chkinfo->id), ret);
                                        YASSERT(ret != EINVAL);
                                        GOTO(err_ret, ret);
                                }
                        }

                        if (__verbose__) {
                                printf("recovery chunk chkid:"CHKID_FORMAT" success.\n", CHKID_ARG(&chkinfo->id));
                        }
                }
        }

        if (__scan__) {
                if (needcheck) {
                        CHKINFO_STR(chkinfo, buf);
                        printf("scan chunk %s\n", buf);
                }

                if (__deep__) {
                        ret = md_chunk_check(parentnid, parent, &chkinfo->id, -1, 0, 1, NULL);
                        if (unlikely(ret)) {
                                if (ret == ECANCELED) {
                                        printf("recovery chunk chkid:"CHKID_FORMAT" fail skip\n", CHKID_ARG(&chkinfo->id));
                                        goto out;
                                } else {
                                        printf("recovery chunk chkid:"CHKID_FORMAT" fail ret:%d\n", CHKID_ARG(&chkinfo->id), ret);
                                        YASSERT(ret != EINVAL);
                                        GOTO(err_ret, ret);
                                }
                        }
                }
        }

out:
        return 0;
err_ret:
        return ret;
}

static void __recovery_file_range__(const char *pool, const nid_t *nid, const fileid_t *fileid, int from, int count, int *_fail)
{
        int ret, i, fail = 0, start, end;
        chkid_t chkid, tmp;
        char buf[MAX_BUF_LEN];
        chkinfo_t *chkinfo;

        DBUG("chunk "CHKID_FORMAT" from %u count %u\n", CHKID_ARG(fileid), from, count);
        
        chkinfo = (void *)buf;
        for (i = 0; i < count; i++) {
                fid2cid(&chkid, fileid, from + i);

                DBUG("chunk "CHKID_FORMAT"\n", CHKID_ARG(&chkid));
                ret = md_chunk_getinfo(pool, fileid, &chkid, chkinfo, NULL);
                if (unlikely(ret)) {
                        if (ret == ENOKEY)
                                continue;
                        else {
                                DWARN("chkid "CHKID_FORMAT"@"CHKID_FORMAT" ret(%d)%s\n",
                                      CHKID_ARG(&chkid), CHKID_ARG(&chkid), ret, strerror(ret));
                                fail++;
                                continue;
                        }
                }

                ret = __recovery_chunk(nid, fileid, chkinfo);
                if (unlikely(ret)) {
                        DBUG("chkid "CHKID_FORMAT" ret(%d)%s\n", CHKID_ARG(&chkid), ret, strerror(ret));
                        fail++;
                }
        }

        /* recovery subvol; from start to end(include end). */
        start = from / FILE_PROTO_EXTERN_ITEM_COUNT;
        end = (from + count - 1) / FILE_PROTO_EXTERN_ITEM_COUNT;

        fid2cid(&tmp, fileid, from);
        cid2tid(&chkid, &chkid);

        for (i = start; i <= end; i++) {
                chkid.idx = i;

                DBUG("chunk "CHKID_FORMAT"\n", CHKID_ARG(&chkid));
                ret = md_chunk_getinfo(pool, fileid, &chkid, chkinfo, NULL);
                if (unlikely(ret)) {
                        if (ret == ENOKEY)
                                continue;
                        else {
                                DWARN("chkid "CHKID_FORMAT"@"CHKID_FORMAT" ret(%d)%s\n",
                                                CHKID_ARG(&chkid), CHKID_ARG(fileid), ret, strerror(ret));
                                fail++;
                                continue;
                        }
                }

retry:
                ret = __recovery_chunk(nid, fileid, chkinfo);
                if (unlikely(ret)) {
                        DWARN("chkid "CHKID_FORMAT" ret(%d)%s\n", CHKID_ARG(fileid), ret, strerror(ret));
                        if (ret == EBUSY) {
                                usleep(100 * 1000);
                                GOTO(retry, ret);
                        } else {
                                fail++;
                        }
                }
        }

        *_fail = fail;
}

typedef struct {
        sem_t sem;
        int from;
        int count;
        nid_t nid;
        fileid_t fileid;
        int fail;
        char pool[MAX_NAME_LEN];
} args_t;

void *__recovery_file_range(void *_args)
{
        args_t *args;

        args = _args;

        __recovery_file_range__(args->pool, &args->nid, &args->fileid, args->from, args->count, &args->fail);

        sem_post(&args->sem);

        return NULL;
}

static int __recovery_file__(const char *pool, const nid_t *nid, const fileid_t *fileid)
{
        int ret, retry = 0, thread, i, running = 0, step = 0, err = 0, size, offset;
        fileinfo_t fileinfo;
        args_t _args[100], *args;
        uint64_t chknum, left;
        pthread_t th;
        pthread_attr_t ta;

retry:
        ret = md_getattr(fileid, &fileinfo);
        if (unlikely(ret)) {
                if (ret == EAGAIN) {
                        USLEEP_RETRY(err_ret, ret, retry, retry, 50, (100 * 1000));
                } else
                        GOTO(err_ret, ret);
        }

        chknum = size2chknum(fileinfo.size, &fileinfo.ec);

        if (__thread__) {
                thread = __thread__;
        } else {
                if (chknum < 100)
                        thread = 1;
                else if (chknum < 1000)
                        thread = 10;
                else
                        thread = 100;
        }

        (void) pthread_attr_init(&ta);
        (void) pthread_attr_setdetachstate(&ta,PTHREAD_CREATE_DETACHED);

        step = _ceil(chknum, thread);
        left = chknum;
        offset = 0;
        while (left) {
                args = &_args[running++];
                ret = sem_init(&args->sem, 0, 0);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                size = _min(left, step);

                args->fail = 0;
                args->nid = *nid;
                args->fileid = *fileid;
                args->from = offset;
                args->count = size;
                strcpy(args->pool, pool);

                ret = pthread_create(&th, &ta, __recovery_file_range, args);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                left -= size;
                offset += size;
        }

        for (i = 0; i < running; i++) {
                args = &_args[i];
                ret = sem_wait(&args->sem);
                if (unlikely(ret))
                        err = ret;

                if (args->fail) {
                        __fail__++;
                }
        }

        if (err) {
                ret = err;
                GOTO(err_ret, ret);
        }

        ret = md_vfm_cleanup(fileid);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

static int __recovery_file(const char *pool, const nid_t *nid, const fileid_t *fileid)
{
        int ret, delen, retry = 0, err_count;
        char buf[MAX_BUF_LEN], de0[BIG_BUF_LEN];
        chkinfo_t *chkinfo;
        struct dirent *de;
        uint64_t offset;
        char uuid[MAX_NAME_LEN] = {};

        char snap_name[MAX_NAME_LEN];
        long snap_ver, snap_from;

        get_uuid(uuid);

        printf("scan volume pool:%s fileid:"CHKID_FORMAT"\n",  pool, CHKID_ARG(fileid));

        ret = __recovery_file__(pool, nid, fileid);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        err_count = 0;
retry:
        delen = BIG_BUF_LEN;
        ret = stor_snapshot_list(fileid, uuid, 0, de0, &delen);
        if (unlikely(ret)) {
                if (ret == EAGAIN) {
                        USLEEP_RETRY(err_ret, ret, retry, retry, 50, (100 * 1000));
                } else
                        GOTO(err_ret, ret);
        }

        if (delen == 0)
                goto out;

        dir_for_each(de0, delen, de, offset) {
                if (strlen(de->d_name) == 0) {
                        break;
                }

                if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
                        continue;
                }

                if (!memcmp(de->d_name, LICH_SYSTEM_ATTR_UNLINK, strlen(LICH_SYSTEM_ATTR_UNLINK))) {
                        continue;
                }

                ret = sscanf(de->d_name, "%s %ld %ld", snap_name, &snap_ver, &snap_from);
                if (ret != 3)
                        continue;

                chkinfo = (void *)buf;
                ret = md_lookup_byname(pool, fileid, snap_name, chkinfo, NULL);
                if (unlikely(ret)) {
                        DERROR("lookup %s of "CHKID_FORMAT" fail, ret: %d\n", de->d_name, CHKID_ARG(fileid), ret);
                        err_count++;
                        continue;
                }

                ret = __recovery_chunk(&chkinfo->diskid[0].id, &chkinfo->id, chkinfo);
                if (unlikely(ret)) {
                        DERROR("recover_chunk fail "CHKID_FORMAT", ret: %d\n", CHKID_ARG(&chkinfo->id), ret);
                        err_count++;
                        continue;
                }

                ret = __recovery_file__(pool, &chkinfo->diskid[0].id, &chkinfo->id);
                if (unlikely(ret)) {
                        DERROR("recover_file fail "CHKID_FORMAT", ret: %d\n",  CHKID_ARG(&chkinfo->id), ret);
                        err_count++;
                        continue;
                }
        }

out:
        if (err_count) {
                ret = EAGAIN;
                GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

static int __recovery_dir__(const char *pool, const nid_t *nid, const fileid_t *fileid, const chkinfo_t *_chkinfo)
{
        int ret, retry;
        uint64_t i;
        chkid_t chkid;
        char buf[MAX_BUF_LEN];
        chkinfo_t *chkinfo;

        ret = __recovery_chunk(nid, fileid, _chkinfo);
        if (unlikely(ret)) {
                if (ret == ENOSPC) {
                        //nothing todo
                } else
                        GOTO(err_ret, ret);
        }

        chkinfo = (void *)buf;
        i = 0;
        while (1) {
                chkid = *fileid;
                chkid.type = __POOL_SUB_CHUNK__;
                chkid.idx = i;

                retry = 0;
        retry:
                ret = md_chunk_getinfo(pool, fileid, &chkid, chkinfo, NULL);
                if (unlikely(ret)) {
                        if (ret == ENOKEY)
                                break;
                        else {
                                if (ret == EAGAIN) {
                                        USLEEP_RETRY(err_ret, ret, retry, retry, 50, (100 * 1000));
                                } else
                                        GOTO(err_ret, ret);
                        }
                }

                ret = __recovery_chunk(nid, fileid, chkinfo);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                i++;
        }

        return 0;
err_ret:
        return ret;
}

static int __recovery_dir(const char *pool, const nid_t *nid, const fileid_t *fileid)
{
        int ret, delen, retry = 0, err_count = 0;
        struct dirent *de;
        char buf[MAX_BUF_LEN], de0[BIG_BUF_LEN];
        uint64_t offset = 0, offset2 = 0;
        chkinfo_t *chkinfo;
        char uuid[MAX_NAME_LEN] = {};

        (void) nid;

        YASSERT(fileid->type == __POOL_CHUNK__);

        if (!chkid_cmp(&unlink_fid, fileid))
                goto out;

        get_uuid(uuid);
        ret = md_listpool_open(fileid, uuid);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        while (1) {
retry:
                delen = BIG_BUF_LEN;
                memset(de0, 0, delen);
                ret = md_listpool(fileid, uuid, offset, de0, &delen);
                if (unlikely(ret)) {
                        if (ret == EAGAIN) {
                                USLEEP_RETRY(err_close, ret, retry, retry, 50, (100 * 1000));
                        } else
                                GOTO(err_close, ret);
                }

                if (delen == 0)
                        break;

                offset2 = 0;
                dir_for_each(de0, delen, de, offset2) {
                        if (strlen(de->d_name) == 0)
                                goto out;
                        else if (delen - offset2 < sizeof(*de) + MAX_NAME_LEN)
                                break;

                        offset += de->d_reclen;

                        if (strcmp(de->d_name, ".") == 0
                                        || strcmp(de->d_name, "..") == 0)
                                continue;

                        chkinfo = (void *)buf;
                        ret = md_lookup_byname(pool, fileid, de->d_name, chkinfo, NULL);
                        if (unlikely(ret)) {
                                DERROR("lookup %s of "CHKID_FORMAT" fail, ret: %d\n", de->d_name, CHKID_ARG(fileid), ret);
                                err_count++;
                                continue;
                        }

                        //ret = md_chunk_check(&chkinfo->diskid[0].id, &chkinfo->id, &chkinfo->id, -1);
                        if (chkinfo->id.type == __VOLUME_CHUNK__) {
                                ret = __recovery_chunk(&chkinfo->diskid[0].id, &chkinfo->id, chkinfo);
                                if (unlikely(ret)) {
                                        DBUG("recovery_chunk fail "CHKID_FORMAT" ret, %d\n", CHKID_ARG(&chkinfo->id), ret);
                                        err_count++;
                                        continue;
                                }

                                ret = __recovery_file(pool, &chkinfo->diskid[0].id, &chkinfo->id);
                                if (unlikely(ret)) {
                                        DERROR("recovery_file fail "CHKID_FORMAT" ret, %d\n", CHKID_ARG(&chkinfo->id), ret);
                                        err_count++;
                                        continue;
                                }
                        } else {
                                ret = __recovery_dir__(pool, &chkinfo->diskid[0].id, &chkinfo->id, chkinfo);
                                if (unlikely(ret)) {
                                        DERROR("recovery_dir fail "CHKID_FORMAT" ret, %d\n", CHKID_ARG(&chkinfo->id), ret);
                                        err_count++;
                                        continue;
                                }

                                ret = __recovery_dir(pool, &chkinfo->diskid[0].id, &chkinfo->id);
                                if (unlikely(ret)) {
                                        DERROR("recovery_dir fail "CHKID_FORMAT" ret, %d\n", CHKID_ARG(&chkinfo->id), ret);
                                        err_count++;
                                        continue;
                                }
                        }
                }
        }

out:
        if (err_count) {
                ret = EAGAIN;
                GOTO(err_close, ret);
        }

        md_listpool_close(fileid, uuid);
        return 0;
err_close:
        md_listpool_close(fileid, uuid);
err_ret:
        DERROR("fail: %d\n", ret);
        return ret;
}

static int __mkunlink(const char *pool)
{
        int ret;
        fileid_t fileid, fileid1, fileid2;

        ret = stor_lookup1(pool, "/", &fileid);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = stor_mkpool(&fileid, "system", NULL, &fileid1);
        if (unlikely(ret)) {
                if (ret == EEXIST) {
                        ret = stor_lookup1(pool, "/system", &fileid1);
                        if (unlikely(ret))
                                GOTO(err_ret, ret);
                } else
                        GOTO(err_ret, ret);
        }

        ret = stor_mkpool(&fileid1, "unlink", NULL, &fileid2);
        if (unlikely(ret)) {
                if (ret == EEXIST) {
                } else
                        GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        if (ret == ENOSPC)
                ret = EAGAIN;
        return _errno(ret);
}

int recovery_bypath(void *_pool, void *arg)
{
        int ret;
        fileid_t fileid;
        chkinfo_t *chkinfo;
        char buf[MAX_BUF_LEN];
        recovery_t *recovery = arg;
        const char *pool = _pool;

        __recovery__ = 1;
        __deep__ = recovery->deep;
        __verbose__ = recovery->verbose;
        __fail__ = 0;
        __lost__ = 0;
        __hasscan__ = 0;
        __thread__ = recovery->thread;

        ret = stor_lookup1(pool, recovery->path, &fileid);
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        printf("scan pool:%s path:%s fileid:"CHKID_FORMAT", thread %u\n", pool, recovery->path, CHKID_ARG(&fileid), __thread__);

retry:
        ret = stor_lookup1(pool, UNLINK_ROOT, &unlink_fid);
        if (unlikely(ret)) {
                if (ret == ENOENT) {
                        ret = __mkunlink(pool);
                        if (unlikely(ret)) {
                                GOTO(err_ret, ret);
                        }

                        goto retry;
                } else
                        GOTO(err_ret, ret);
        }

        if (!chkid_cmp(&unlink_fid, &fileid))
                goto out;

        chkinfo = (void *)buf;
        ret = md_chunk_getinfo(pool, NULL, &fileid, chkinfo, NULL);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = __recovery_chunk(&chkinfo->diskid[0].id, &chkinfo->id, chkinfo);
        if (unlikely(ret)) {
                DWARN("pool %s\n", pool);
                GOTO(err_ret, ret);
        }

        if (fileid.type == __POOL_CHUNK__) {
                ret = __recovery_dir(pool, &chkinfo->diskid[0].id, &chkinfo->id);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        } else {
                ret = __recovery_file(pool, &chkinfo->diskid[0].id, &chkinfo->id);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        if (__fail__ == 0) {
                printf("%s scan complete successfully.\n", pool);
        } else {
                ret = EAGAIN;
                GOTO(err_ret, ret);
        }

out:
        return 0;
err_ret:
        printf("warning, %s scan fail, fail: %u, lost: %u, please retry again.\n", pool, __fail__, __lost__);
        return ret;
}

int recovery_scan(void *_pool, void *arg)
{
        int ret;
        fileid_t fileid;
        chkinfo_t *chkinfo;
        char buf[MAX_BUF_LEN];
        recovery_t *recovery = arg;
        const char *pool = _pool;

        __scan__ = 1;
        __deep__ = recovery->deep;
        __verbose__ = recovery->verbose;
        __fail__ = 0;
        __lost__ = 0;
        __hasscan__ = 0;
        __thread__ = recovery->thread;

        ret = stor_lookup1(pool, recovery->path, &fileid);
        if (unlikely(ret))
                GOTO(err_ret, ret);

retry:
        ret = stor_lookup1(pool, UNLINK_ROOT, &unlink_fid);
        if (unlikely(ret)) {
                if (ret == ENOENT) {
                        ret = __mkunlink(pool);
                        if (unlikely(ret)) {
                                GOTO(err_ret, ret);
                        }

                        goto retry;
                } else
                        GOTO(err_ret, ret);
        }

        if (!chkid_cmp(&unlink_fid, &fileid))
                goto out;

        chkinfo = (void *)buf;
        ret = md_chunk_getinfo(pool, NULL, &fileid, chkinfo, NULL);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = __recovery_chunk(&chkinfo->diskid[0].id, &chkinfo->id, chkinfo);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if (fileid.type == __POOL_CHUNK__) {
                ret = __recovery_dir(pool, &chkinfo->diskid[0].id, &chkinfo->id);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        } else {
                ret = __recovery_file(pool, &chkinfo->diskid[0].id, &chkinfo->id);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        if ((__needcheck__ != 0 && __recovery__) || __lost__ != 0) {
                ret = EAGAIN;
                printf("need recover: %u\n", __needcheck__);
                GOTO(err_ret, ret);
        }

        if (__fail__ == 0) {
                printf("%s scan complete successfully.\n", pool);
        } else {
                ret = EAGAIN;
                GOTO(err_ret, ret);
        }

out:
        return 0;
err_ret:
        printf("warning, %s scan fail, unintact %u, lost: %u, please retry again\n", pool, __needcheck__, __lost__);
        return ret;
}
